/// <reference types="node" />
/// <reference types="mz" />

import { Server, Socket, I } from 'libtcp';
import config = require('./config');
import tool = require('./tool');
import path = require('path');
import mzfs = require('mz/fs');

export type Fileinfo = config.Fileinfo;
export type BeforeReceiveFunction = (socket: Socket, fileinfo: Fileinfo, message: any) => Promise<boolean>; // return true if accept
export type ReceivedFunction = (socket: Socket, fileinfo: Fileinfo, message: any, content: Buffer) => any;

// 如果文件请求被接受，则resolve(path)
export type FileRequestFunction = (socket: Socket, message: any) => Promise<string>;

export class FSServer {
	private g_index: number = 0;
	private before_receive: BeforeReceiveFunction = null;
	private received: ReceivedFunction = null;
	private on_file_request: FileRequestFunction = null;

	constructor(private server: Server) {
		server.onSocketSync(config.FILE_MESSAGE, (socket: Socket, arg: { fileinfo: config.Fileinfo, message: any }) => {
			let index = this.g_index = (this.g_index + 1) % config.MOD;
			let before = this.before_receive == null ? null : (fileinfo: Fileinfo, message: any) => {
				return this.before_receive(socket, fileinfo, message);
			}
			let ed = this.received == null ? null : (fileinfo: Fileinfo, message: any, content: Buffer) => {
				return this.received(socket, fileinfo, message, content);
			}
			return tool.receiveFile(index, arg.fileinfo, arg.message, socket, before, null, ed);
		});
		server.onSocketSync(config.FILE_REQUEST_MESSAGE, (socket: Socket, message: any) => {
			return new Promise(async (resolve, reject) => {
				let filename: string = null;
				let fileinfo: config.Fileinfo = null;
				try {
					if (!this.on_file_request) {
						return reject(new Error('can not request file'));
					}
					try {
						filename = await this.on_file_request(socket, message);
					} catch (e) {
						return reject('file request not accept with error : ' + e);
					}
					if (filename == null) {
						return reject(new Error('file request not accept'));
					}
					let stat = await mzfs.stat(filename);
					if (!stat.isFile()) {
						return reject(new Error('only file can be sent'));
					}
					fileinfo = {
						filename: path.basename(filename),
						total_size: stat.size
					};
				} catch (e) {
					return reject(e);
				}

				// start send file
				let index = this.g_index = (this.g_index + 1) % config.MOD;
				const CONTENT_MESSAGE = config.FILE_REQUEST_MESSAGE + index + '!!';
				const CONTENT_END_MESSAGE = config.FILE_REQUEST_MESSAGE + index + '!!end';
				resolve(index);

				try {
					await socket.waitForEvent(CONTENT_MESSAGE);
					socket.emit(CONTENT_MESSAGE, fileinfo);
					let fd = await mzfs.open(filename, "r");
					let sent_size = 0;
					while (sent_size < fileinfo.total_size) {
						let ret = await mzfs.read(fd, new Buffer(config.BUFFER_SIZE), 0, config.BUFFER_SIZE, sent_size);
						socket.emit(CONTENT_MESSAGE, ret[1].slice(0, ret[0]));
						sent_size += ret[0];
					}
					socket.emit(CONTENT_END_MESSAGE);
				} catch (e) {
					socket.emit(CONTENT_END_MESSAGE);
					throw e;
				}
			});
		});
	}

	public setBefore(before: BeforeReceiveFunction) {
		this.before_receive = before;
	}

	public setReceived(received: ReceivedFunction) {
		this.received = received;
	}

	public setOnFileRequest(on_file_request: FileRequestFunction) {
		this.on_file_request = on_file_request;
	}
}
